package com.hmx.openplatform.gateway.filters;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hmx.openplatform.commons.constans.BaseResultBean;
import com.hmx.openplatform.commons.constans.ExceptionDict;
import com.hmx.openplatform.commons.constans.SystemParams;
import com.hmx.openplatform.gateway.feign.CacheService;
import com.hmx.openplatform.gateway.listener.InitApiRouting;
import com.netflix.discovery.converters.Auto;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.Map;

/**
 * Description:
 * 当前过滤器的主要作用是根据用户传递的标识来获取到用户实际想要访问的服务的id和地址，然后再进行转发
 * @author hmx
 * @date Created on 2021/5/23
 */
@Component
public class RoutingFilter extends ZuulFilter {
    @Autowired
    private CacheService cacheService;

    @Autowired
    private ObjectMapper objectMapper;
    /**
     * 当前过滤器是在我们请求的时候执行的,而我们所有的请求都是转到了缓存服务，我们应该在转发之前就将请求转到另外的服务，所以是前置过滤器
     * @return
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        //我们刚才分析过，我们这个过滤器是在前面的各种校验过滤器成功之后才执行的，所以它理论上是最后一个前置过滤器，所以order稍微高一些
        return 100;
    }

    @Override
    public boolean shouldFilter() {
        //return true;
        //根据之前的过滤器结果来决定是否启用当前过滤器
        return RequestContext.getCurrentContext().sendZuulResponse();
    }

    @Override
    public Object run() throws ZuulException {
        //如何拿到请求对象
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        //从请求中获取用户的标识数据
        //我们应该从用户的请求参数中获取这个数据,那么请求的参数名是什么,我们怎么知道,所以我们作为服务端就要定义规则,我们要求用户必须通过method参数来传递
        String method = request.getParameter("method");
        //从rendis根据用户传递的标识获取路由信息
        try {
            //获取当前参数的路由映射关系map,这里面放的就是我们的标识对应的服务的id和地址等信息,根据这个信息我们可以拿到要访问的地址和服务id,然后进行服务的跳转
            //Map<Object, Object> apiMapingInfo = cacheService.hGetAll(SystemParams.METHOD_REDIS_PRE + method);
            //---------------------------------华丽的分割线----------------------

            //因为我们已经在本地保存了一份缓存数据,所以我们可以从本地先找一次
            Map<Object, Object> apiMapingInfo = InitApiRouting.API_ROUTING_MAP_CACHE.get(SystemParams.METHOD_REDIS_PRE + method);
            if (apiMapingInfo == null) {
                //如果是空的,就先去缓存中查询一次
                apiMapingInfo = cacheService.hGetAll(SystemParams.METHOD_REDIS_PRE + method);
                InitApiRouting.API_ROUTING_MAP_CACHE.put(SystemParams.METHOD_REDIS_PRE + method, apiMapingInfo);//保存到本地一份,当然可能是空的
            }
            //获取服务id和地址
            if (apiMapingInfo != null&&apiMapingInfo.size()>0) {
                Object serviceId = apiMapingInfo.get("serviceId");//我们要访问的服务的id
                String insideApiUrl = (String) apiMapingInfo.get("insideApiUrl");//我们要访问的地址
                //通过reqyestcontext设置我们的请求服务id和地址即可
                //设置我们要访问的地址
                currentContext.put(FilterConstants.SERVICE_ID_KEY, serviceId);
                //我们请求中传递的普通参数会一起跟随转发过去
                //但是经过我们的测试,我们的rest风格的请求是无法实现的转发的,因为我们在定义请求映射的时候就通过占位符来声明了请求的路径, 比如 test01_02 对应的是/testservice01/test02/{name}这个地址
                //我们应该将/testservice01/test02/{name}中的{name}实际替换为我们的真正的参数,否则我们的请求就会吧{name}作为真正的参数传递到下游的服务中,导致参数出现问题
                //我们应该想办法替换掉参数,怎么替换呢?我们需要知道用户请求的参数的中一一对应的关系,比如用户传递了name age两个参数,我们如何区分哪个对应哪个
                //所以我们作为服务端要开始定义规则,我们定义的规则是我们占位符中的名字和请求参数中的名字必须保持一致,这个我们只需要将请求参数中对应的值替换掉占位符就可以了
                //比如我们服务请求的地址是/testservice01/test02/{name},那么用户必须传递一个叫name的参数比如name=lisi,我们只需要将lisi替换掉{name}就可以
                //因为我们不清楚到底有什么占位符,所以比较难题换,所以我们使用逆向思维,我们看看用户传递了什么请求参数,将请求参数进行遍历,然后看看这个参数有没有对应的占位符,有的话就替换掉
                Enumeration<String> parameterNames = request.getParameterNames();//获取所有的参数名

                while (parameterNames.hasMoreElements()) {
                    String paramName = parameterNames.nextElement();//获取当前的参数名
                    insideApiUrl = insideApiUrl.replace("{" + paramName + "}", request.getParameter(paramName));//将符合当前遍历的参数名的占位符替换为请求参数的值
                }

                currentContext.put(FilterConstants.REQUEST_URI_KEY, insideApiUrl);
                return null;//返回值没有任何意义
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //说明路由失败,拦截请求,返回错误信息
        currentContext.setSendZuulResponse(false);
        HttpServletResponse response = currentContext.getResponse();
        //   response.setContentType("text/html;charset=utf-8");
        //currentContext.setResponseBody("路由失败,检查参数");
        BaseResultBean bean = new BaseResultBean();
        bean.setCode(ExceptionDict.ROUTING_ERROR);
        bean.setMsg("与 "+method+" 相关的服务没有找到,请确认后再重试");
        response.setContentType("appliaction/json;charset=utf-8");
        try {
            currentContext.setResponseBody(objectMapper.writeValueAsString(bean));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
}
